const { EventEmitter } = require('node:events');
const http = require('node:http');
const crypto = require('node:crypto');


function hashKey(key) {
    const sha1 = crypto.createHash('sha1');
    sha1.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
    return sha1.digest('base64');
}

function handleMask(maskBytes, data) {
    const payload = Buffer.alloc(data.length);
    for (let i = 0; i < data.length; i++) {
        payload[i] = maskBytes[i % 4] ^ data[i];
    }
    return payload;
}

function encodeMessage(opcode, payload) {
    // 我们只处理数据长度小于 125 的情况。
    //payload.length < 126
    let bufferData = Buffer.alloc(payload.length + 2 + 0);
    let byte1 = parseInt('10000000', 2) | opcode; // 设置 FIN 为 1
    let byte2 = payload.length;
    bufferData.writeUInt8(byte1, 0);
    bufferData.writeUInt8(byte2, 1);
    payload.copy(bufferData, 2);
    return bufferData;
}

const OPCODES = {
    CONTINUE: 0,
    TEXT: 1, // 文本
    BINARY: 2, // 二进制
    CLOSE: 8,
    PING: 9,
    PONG: 10,
};

// 继承 EventEmitter 是为了可以用 emit 发送一些事件，外界可以通过 on 监听这个事件来处理
class MyWebsocket extends EventEmitter {
    constructor(options) {
        super(options);
        const server = http.createServer();
        server.listen(options.port || 8080);
        // 创建了一个 http 服务，当 ungrade 事件发生，也就是收到了 Connection: upgrade 的 header 的时候，返回切换协议的 header
        // 返回的 header 前面已经见过了，就是要对 sec-websocket-key 做下处理
        server.on('upgrade', (req, socket) => {
            this.socket = socket;
            socket.setKeepAlive(true);
            const resHeaders = [
                'HTTP/1.1 101 Switching Protocols',
                'Upgrade: websocket',
                'Connection: Upgrade',
                'Sec-WebSocket-Accept: ' + hashKey(req.headers['sec-websocket-key']),
                '',
                ''
            ].join('\r\n');
            socket.write(resHeaders);
            socket.on('data', (data) => {
                this.processData(data);
                // console.log(data)
            });
            socket.on('close', (error) => {
                this.emit('close');
            });
        });
    }

    // 这样的协议格式的数据叫做 frame，也就是帧
    processData(bufferData) {
        // 读取 8 位无符号整数的内容，也就是一个字节的内容,参数是偏移的字节，这里是 0。
        const byte1 = bufferData.readUInt8(0);
        let opcode = byte1 & 0x0f;
        // 然后再处理第二个字节
        const byte2 = bufferData.readUInt8(1);
        // 括号中的参数 2 指定了转换时使用的基数，即转换为二进制字符串。
        const str2 = byte2.toString(2)
        const MASK = str2[0];
        let payloadLength = parseInt(str2.substring(1), 2);
        // 后面咋还有俩 payload 长度呢？这是因为数据不一定有多长，可能需要 16 位存长度，可能需要 32 位。
        // 于是 websocket 协议就规定了如果那个 7 位的内容不超过 125，那它就是 payload 长度。
        // 如果 7 位的内容是 126，那就不用它了，用后面的 16 位的内容作为 payload 长度。
        // 如果 7 位的内容是 127，也不用它了，用后面那个 64 位的内容作为 payload 长度。
        // curByteIndex 是存储当前处理到第几个字节的。
        let curByteIndex = 2;
        if (payloadLength === 126) {
            payloadLength = bufferData.readUInt16BE(2);
            curByteIndex += 2;
        } else if (payloadLength === 127) {
            payloadLength = bufferData.readBigUInt64BE(2);
            curByteIndex += 8;
        }

        // 这样就拿到了 payload 的长度，然后再用这个长度去截取内容就好了。
        // 但在读取数据之前，还有个 mask 要处理，这个是用来给内容解密的
        // 读 4 个字节，就是 mask key。
        // 再后面的就可以根据 payload 长度读出来。
        let realData = null;
        if (MASK) {
            const maskKey = bufferData.slice(curByteIndex, curByteIndex + 4);
            curByteIndex += 4;
            const payloadData = bufferData.slice(curByteIndex, curByteIndex + payloadLength);
            // 然后用 mask key 来解密数据。
            // 这个算法也是固定的，用每个字节的 mask key 和数据的每一位做按位异或就好了
            realData = handleMask(maskKey, payloadData);
        } else {
            realData = bufferData.slice(curByteIndex, curByteIndex + payloadLength);
        }

        this.handleRealData(opcode, realData);
    }

    handleRealData(opcode, realDataBuffer) {
        switch (opcode) {
            // 文本就转成 utf-8 的字符串，二进制数据就直接用 buffer 的数据。
            case OPCODES.TEXT:
                this.emit('data', realDataBuffer.toString('utf8'));
                break;
            case OPCODES.BINARY:
                this.emit('data', realDataBuffer);
                break;
            default:
                this.emit('close');
                break;
        }
    }

    send(data) {
        let opcode;
        let buffer;
        // 判断 data 是否是一个 Buffer 对象。
        if (Buffer.isBuffer(data)) {
            opcode = OPCODES.BINARY;
            buffer = data;
        } else if (typeof data === 'string') {
            opcode = OPCODES.TEXT;
            buffer = Buffer.from(data, 'utf8');
        } else {
            console.error('暂不支持发送的数据类型')
        }
        this.doSend(opcode, buffer);
    }
    doSend(opcode, bufferDatafer) {
        this.socket.write(encodeMessage(opcode, bufferDatafer));
    }
}

module.exports = MyWebsocket